/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.excel.read;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.star.framework.excel.core.Column;
import cn.star.framework.excel.core.RowWrapper;
import cn.star.framework.excel.read.status.SheetStatus;
import cn.star.framework.excel.read.status.ReadStatus;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.Cell;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.util.ConverterUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;

/**
 * 对每个Sheet进行处理<br>
 * 对每个Sheet中的表格进行处理<br>
 * 对读取到的每一行数据进行处理<br>
 * 仅支持读取一类数据<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Slf4j
public class SimpleAnalysisEventListener extends AnalysisEventListener<Object> {

  private final ReadRowCallback callback;
  private final ReadContext context;
  private final ReadRowConverter converter;

  /** 当前头信息 */
  private final Map<Integer, Column> currentHeaderMap = new HashMap<>();

  /**
   * 临时数据<br>
   * 以表格为单位
   */
  private final List<RowWrapper> wrappers;

  public SimpleAnalysisEventListener(ReadContext context, ReadRowCallback callback) {
    this(context, callback, new SimpleReadRowConverter());
  }

  public SimpleAnalysisEventListener(
      ReadContext context, ReadRowCallback callback, ReadRowConverter converter) {
    this.context = context;
    this.callback = callback;
    this.converter = converter;

    this.wrappers = new ArrayList<>();
  }

  private void setReadContextSheetStatus(AnalysisContext analysisContext, ReadStatus status) {
    this.setReadContextSheetStatus(analysisContext, status, null);
  }

  private void setReadContextSheetStatus(
      AnalysisContext analysisContext, ReadStatus status, Exception e) {
    String name = analysisContext.readSheetHolder().getSheetName();
    List<SheetStatus> sheetStatuses = this.context.getSheetStatuses();
    SheetStatus sheetStatus =
        sheetStatuses.stream().filter(i -> name.equals(i.getName())).findFirst().orElse(null);
    if (sheetStatus == null) {
      sheetStatus = new SheetStatus(name);
      sheetStatuses.add(sheetStatus);
    }
    sheetStatus.setStatus(status);
    if (e != null) {
      sheetStatus.setErrorType(e.getClass().getName());
      sheetStatus.setMessage(e.getMessage());
    }
  }

  private void setReadContextStatus(ReadStatus status) {
    this.context.setStatus(status);
  }

  /**
   * 发生读取异常时被调用
   *
   * @param exception
   * @param analysisContext
   * @throws Exception
   */
  @Override
  public void onException(Exception exception, AnalysisContext analysisContext) throws Exception {
    this.setReadContextStatus(ReadStatus.UNFINISHED);
    this.setReadContextSheetStatus(analysisContext, ReadStatus.UNFINISHED, exception);
    this.callback.callback(null, context, analysisContext, exception);
    // 异常需要抛出取，否则会一直读取下去
    throw exception;
  }

  /**
   * 当解析到Excel表格头部时会被调用<br>
   * 在第一行不为空的情况下才会进入到此方法<br>
   * 可以获取到表格头部信息，进行一些校验或者处理操作<br>
   *
   * @param headMap
   * @param analysisContext
   */
  @Override
  public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext analysisContext) {
    this.setReadContextStatus(ReadStatus.READING);
    this.setReadContextSheetStatus(analysisContext, ReadStatus.READING);
    // 计算当前表头
    this.calculateCurrentHeaderMap(analysisContext);
    if (CollectionUtil.isNotEmpty(currentHeaderMap)) {
      log.info("first row is header");
    }
  }

  /**
   * 当解析到Excel表格数据时会被调用<br>
   * 控制粒度：行<br>
   *
   * @param data
   * @param analysisContext
   */
  @Override
  public void invoke(Object data, AnalysisContext analysisContext) {
    // 读取数据
    this.setReadContextStatus(ReadStatus.READING);
    this.setReadContextSheetStatus(analysisContext, ReadStatus.READING);
    // 计算当前表头
    boolean header = this.calculateCurrentHeaderMap(analysisContext);
    if (CollectionUtil.isEmpty(currentHeaderMap)) {
      log.warn(
          "Not have header, skip, sheet: {}, row index: {}, data: {}",
          analysisContext.readSheetHolder().getSheetName(),
          analysisContext.readRowHolder().getRowIndex(),
          data);
      return;
    }
    if (header) {
      return;
    }
    RowWrapper wrapper = this.wrapper(currentHeaderMap, data, analysisContext);
    this.wrappers.add(wrapper);
    // 使用回调函数处理一批数据
    if (this.wrappers.size() >= this.context.getBatchSize()) {
      this.callback.callback(this.wrappers, this.context, analysisContext);
      this.wrappers.clear();
    }
  }

  /**
   * 将数据行转换包装<br>
   * 默认{@link SimpleReadRowConverter}将行数据转换成Map，若重写系方法则根绝类型 T 来转换
   *
   * @param currentHeaderMap
   * @param data
   * @param analysisContext
   * @return
   */
  protected RowWrapper wrapper(
      Map<Integer, Column> currentHeaderMap, Object data, AnalysisContext analysisContext) {
    return this.converter.convert(currentHeaderMap, data, analysisContext);
  }

  private static class NotFoundHeaderException extends RuntimeException {
    public NotFoundHeaderException(String message) {
      super(message);
    }
  }

  /**
   * 当一个Sheet处理完成时会调用<br>
   *
   * @param analysisContext
   */
  @Override
  public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    try {
      // 处理当前缓存中的数据，需要做收尾判断，即当前 sheet 完成、当前 excel 是否完成判断
      this.callback.callback(this.wrappers, this.context, analysisContext);

      // 获取 sheet 页总数
      this.setReadContextSheetStatus(analysisContext, ReadStatus.FINISH);

      // 当前 sheet 页码
      if (this.context.getSheetCount() - 1 == analysisContext.readSheetHolder().getSheetNo()) {
        // 处理完最后一个 Sheet 表示完成
        this.setReadContextStatus(ReadStatus.FINISH);
      }

      if (CollectionUtil.isEmpty(this.currentHeaderMap)) {
        throw new NotFoundHeaderException(
            "Standard header information not found, data read failed");
      }
      // 处理后就是下一个 sheet 的开始
    } catch (Exception e) {
      if (e instanceof NotFoundHeaderException) {
        this.setReadContextStatus(ReadStatus.UNFINISHED);
        this.context.setThrowable(e);
      }

      log.error(e.getMessage());
    } finally {
      this.wrappers.clear();
      this.currentHeaderMap.clear();
    }
  }

  @Override
  public boolean hasNext(AnalysisContext analysisContext) {
    return this.context.getStatus() == ReadStatus.READING;
  }

  @SuppressWarnings("rawtypes")
  private Map<Integer, String> getContextCurrentRecord(AnalysisContext analysisContext) {
    Map<Integer, Cell> cellMap = analysisContext.readRowHolder().getCellMap();
    Map<Integer, ReadCellData<?>> readCellDataHashMap = new HashMap<>(cellMap.size());
    cellMap.forEach((index, cell) -> readCellDataHashMap.put(index, (ReadCellData) cell));

    Map<Integer, String> strMap =
        ConverterUtils.convertToStringMap(readCellDataHashMap, analysisContext);

    Map<Integer, String> map = new HashMap<>(strMap.size());
    strMap.forEach(
        (index, value) -> {
          if (StrUtil.isNotBlank(value)) {
            map.put(index, value);
          }
        });

    return map;
  }

  /**
   * 严格检测<br>
   * 自动检测是否为表头<br>
   * 若是表头，则重新计算列的顺序<br>
   *
   * @param analysisContext {@link AnalysisContext}
   * @return 是否为头
   */
  private boolean calculateCurrentHeaderMap(AnalysisContext analysisContext) {
    if (CollectionUtil.isNotEmpty(currentHeaderMap)) {
      return false;
    }

    boolean header = false;
    List<Column> columns = this.context.getIncludeColumns();
    Map<Integer, String> strMap = this.getContextCurrentRecord(analysisContext);

    Map<Integer, Column> headerColumnMap = new HashMap<>(strMap.size());
    strMap.forEach(
        (index, value) -> {
          columns.stream()
              .filter(i -> value.equals(i.getFirstName()))
              .findFirst()
              .ifPresent(column -> headerColumnMap.put(index, column));
        });
    // 自动判断头，进行对列重新进行列的计算
    // 表头信息长度和列一致，则为标准头 || 兼容头很短或很长的情况
    if (headerColumnMap.size() == columns.size() || headerColumnMap.size() == strMap.size()) {
      // 归零
      columns.forEach(column -> column.setOrder(-1));
      headerColumnMap.forEach(
          (order, column) -> {
            column.setOrder(order);
            currentHeaderMap.put(order, column);
          });
      header = true;
    }

    return header;
  }
}
